/*
 Expert Backup Folders
 Version 3.3 - 22:10 22/09/2016
 By Ben a.k.a DreamVB
*/

#include <iostream>
#include <Windows.h>
#include <string>
#include <algorithm>
#include <conio.h>

using namespace std;
using std::cout;
using std::endl;

//Define program version
#define version 3.3

enum TAction
{
	F_COPY = 0,
	F_MOVE = 1
};

int Copyed = 0;
int Skipped = 0;
//
bool OverReadOnly = false;
bool bCopyFiles = true;
bool NoMsg = false;
bool ShowFullName = false;
bool ShowStatusInfo = false;
bool ShowStartMsg = false;
bool SetAchiveOnlyAttr = false;
bool IncludeFullRelativePath = false;
bool ZeroLenFiles = false;
bool DateStamp = false;
bool UseWinCopy = false;
TAction m_Action;

string sStatusA = "";
string sStatusB = "";
//
string DateToStr(){
	SYSTEMTIME st;
	char lzDate[80];
	char digit[10];
	string sDate = "";
	//Get local system time
	GetLocalTime(&st);
	//Convert date to string
	itoa(st.wMonth, digit, 10);
	strcpy(lzDate, digit);
	strcat(lzDate, "-");
	itoa(st.wDay, digit, 10);
	strcat(lzDate, digit);
	strcat(lzDate, "-");
	itoa(st.wYear, digit, 10);
	strcat(lzDate, digit);
	//Return the data
	return lzDate;
}

string WStrToStr(wstring src){
	string str(src.begin(), src.end());
	return str;
}

wstring StrToWStr(string src){
	//Convert string to wide string
	wstring wstr(src.begin(), src.end());
	//return wide string
	return wstr;
}

bool CreateZeroLengthFile(string src){
	bool IsGood = true;
	FILE *fp = NULL;
	//Just creats an empry file.
	fp = fopen(src.c_str(), "w");
	if (!fp){
		IsGood = false;
	}
	//Close file.
	fclose(fp);
	return IsGood;
}

string TrimR(string src){
	// trim trailing spaces
	size_t endpos = src.find_last_not_of(" \t");
	if (string::npos != endpos)
	{
		src = src.substr(0, endpos + 1);
	}
	return src;
}

string TrimL(string src){
	// trim leading spaces
	size_t startpos = src.find_first_not_of(" \t");
	if (string::npos != startpos)
	{
		src = src.substr(startpos);
	}
	return src;
}

string TrimAll(string src){
	return TrimL(TrimR(src));
}

string LCase(string src)
{
	string Temp = src;
	std::transform(Temp.begin(), Temp.end(), Temp.begin(), ::tolower);
	return Temp;
}

string FixPath(string src){
	if (src[src.length() - 1] != '\\'){
		return src + "\\";
	}
	return src;
}

bool FileExists(string filename){
	//Check if a file was found.
	if (GetFileAttributes(StrToWStr(filename).c_str()) == INVALID_FILE_ATTRIBUTES){
		return false;
	}
	return true;
}

bool DirExists(string filename){
	if ((GetFileAttributes(StrToWStr(filename).c_str()) != FILE_ATTRIBUTE_DIRECTORY)){
		return false;
	}
	return true;
}

bool MyFileCopy(string src, string dest, int chunksize = 2048){
	FILE *fin = NULL;
	FILE *fout = NULL;
	char *Buffer = NULL;
	size_t BytesRead = 0;
	size_t BytesWrite = 0;

	//Open source file.
	fin = fopen(src.c_str(), "rb");

	if (!fin){
		return false;
	}

	fout = fopen(dest.c_str(), "wb");

	if (!fout){
		fclose(fin);
		return false;
	}

	//Resize Buffer
	Buffer = new char[chunksize];
	//Do file copy.

	//Get first data
	BytesRead = fread(Buffer, sizeof(char), chunksize, fin);

	//While not end of file read
	while (!feof(fin)){
		//Write data
		BytesWrite = fwrite(Buffer, sizeof(char), BytesRead, fout);
		//Read in new data.
		BytesRead = fread(Buffer, sizeof(char), chunksize, fin);
	}
	//Write remaining data.
	BytesWrite = fwrite(Buffer, sizeof(char), BytesRead, fout);

	//Check that both read and write bytes are the same.
	if (BytesWrite != BytesRead){
		return false;
	}

	//Tidy up time.
	fclose(fout);
	fclose(fin);

	delete[]Buffer;
	return true;
}

void MyCreateDir(string PathName){
	int i = 0;
	int j = 0;
	string sPart = "";
	string sDir = "";

	//Append end backslash.
	if (!PathName[PathName.length() - 1] != '\\'){
		PathName += "\\";
	}
	//
	while (i < PathName.length()){
		if (PathName[i] == '\\'){
			//The path to create
			sDir += sPart + "\\";
			//Check for folder.
			if ((GetFileAttributes(StrToWStr(sDir).c_str()) & FILE_ATTRIBUTE_DIRECTORY)){
				//Create folder
				CreateDirectory(StrToWStr(sDir).c_str(), NULL);
			}
			//Clear parts.
			sPart.clear();
		}
		else{
			//Split the string by backslash
			sPart += PathName[i];
		}
		//INC counter
		i++;
	}
	sPart.clear();
	sDir.clear();
}

bool BackupAction(TAction action, string srcfile, string destfile, bool bFail){
	bool IsGood = false;

	//Copy file with out moveing it
	if (action == F_COPY){
		//IsGood = CopyFileA(srcfile.c_str(), destfile.c_str(), bFail);
		if (ZeroLenFiles){
			//Create zero size file.
			IsGood = CreateZeroLengthFile(destfile.c_str());
		}
		else{
			//Test if using windows api filecopy
			if (UseWinCopy){
				//Use windows api copy
				IsGood = CopyFile(StrToWStr(srcfile).c_str(), 
					StrToWStr(destfile).c_str(), bFail);
			}
			else{
				//Buffered file copy.
				IsGood = MyFileCopy(srcfile.c_str(), destfile.c_str(), 2048);
			}
		}
	}
	//Copys a file and delets from the source folder.
	if (action == F_MOVE){
		IsGood = MoveFile(StrToWStr(srcfile).c_str(), StrToWStr(destfile).c_str());
	}
	return IsGood;
}

void DirTree(string Path, string lzDestPath, string FileType, bool AllDirs){
	HANDLE f = 0;
	//WIN32_FIND_DATAA wfd;
	WIN32_FIND_DATAW wfd;
	int pos = string::npos;
	string lzPath = "";
	string lzCopyTo = "";
	string lzScan = "";
	string NewPath = "";
	string lzSrcFile = "";
	string lzDestFile = "";
	string Temp = "";
	string fExt = "";

	//Append backslash to path.
	lzPath = FixPath(Path);
	lzScan = lzPath + "*.*";
	//Find first file.
	f = FindFirstFile(StrToWStr(lzScan).c_str(), &wfd);

	//Check for inaild path.
	if (f == INVALID_HANDLE_VALUE){
		cout << "Folder or files not found." << endl << lzPath.c_str() << endl;
		FindClose(f);
		exit(1);
	}

	//Gets the list of files
	do{
		if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
			//Skip dots eg . and ..
			if ((wcsicmp(wfd.cFileName, TEXT(".")) == 0) | (wcsicmp(wfd.cFileName, TEXT("..")) == 0)){
				continue;
			}

			//Fix and build path.
			NewPath = FixPath(lzPath + WStrToStr(wfd.cFileName));

			//Scan all sub folders.
			if (AllDirs){
				//Check if the folder is found/
				if (DirExists(NewPath)){
					//Recall DirTree get the next files
					DirTree(NewPath, lzDestPath, FileType, AllDirs);
				}
			}
		}
		else{
			if (FileType.length() != 0){
				fExt = WStrToStr(wfd.cFileName);
				pos = fExt.find_last_of('.');
				if (pos != string::npos){
					//Erase filename keep file type .ext
					fExt.erase(0, pos);
					//Compare file types
					if (stricmp(fExt.c_str(), FileType.c_str()) == 0){
						lzSrcFile = lzPath + WStrToStr(wfd.cFileName);
					}
				}
			}
			else{
				//Set src filename
				lzSrcFile = lzPath + WStrToStr(wfd.cFileName);
			}
			//Test if we have a filename.
			if (lzSrcFile.length() != 0){
				//Set dest filename
				Temp = lzSrcFile;
				pos = Temp.find_first_of('\\');
				if (pos != string::npos){
					Temp.erase(0, pos + 1);
				}

				//Used for Relative paths
				if (!IncludeFullRelativePath){
					pos = Temp.find_first_of('\\');
					if (pos != string::npos){
						Temp.erase(0, pos);
					}
				}

				//Set dest filename
				lzDestFile = lzDestPath + Temp;

				//Extract the dest full path.
				pos = lzDestFile.find_last_of('\\');
				if (pos != string::npos){
					lzCopyTo = lzDestFile.substr(0, pos);
				}

				//Create destination folders
				if (!DirExists(lzCopyTo)){
					MyCreateDir(lzCopyTo);
				}

				//Check if copying files
				if (bCopyFiles){
					//Check if writeing over old file.
					if (OverReadOnly){
						if (FileExists(lzDestFile)){
							//Remove all file attrib and set to normal
							SetFileAttributes(StrToWStr(lzDestFile).c_str(), FILE_ATTRIBUTE_NORMAL);
						}
					}

					//Copy the src file over to the folder.
					if (!BackupAction(m_Action, lzSrcFile.c_str(), lzDestFile.c_str(), false)) {
						if (!NoMsg){
							if (ShowFullName){
								wcout << lzDestFile.c_str() << StrToWStr(sStatusB) << endl;
							}
							else{
								wcout << wfd.cFileName << StrToWStr(sStatusB) << endl;
							}
							Skipped++;
						}
					}
					else{
						if (!NoMsg){
							if (ShowFullName){
								wcout << lzDestFile.c_str() << StrToWStr(sStatusA) << endl;
							}
							else{
								wcout << wfd.cFileName << StrToWStr(sStatusA) << endl;
							}
							Copyed++;
						}

						//Set file attrib to achive
						if (SetAchiveOnlyAttr){
							SetFileAttributes(StrToWStr(lzDestFile).c_str(), FILE_ATTRIBUTE_ARCHIVE);
						}
					}
					lzSrcFile.clear();
					Temp.clear();
				}
			}
		}
	} while (FindNextFile(f, &wfd) != 0);

	//Close file
	FindClose(f);
}

int main(int argc, char *argv[]){
	string Temp = "";
	string DeskFolder = "";
	string fExt = "";
	string Flags = "";
	bool ScanAllDirs = false;
	int pos = string::npos;
	int i = 3;
	m_Action = F_COPY;

	//Check for help switch
	if (argc == 2){
		Flags = argv[1];
		//If help switch display online help
		if (Flags == "/?"){
			cout << "-------------------------------------------------------------------------------" << endl;
			cout << "  Backup Folders version " << version << " :: Expert Backup Copy For Windows" << endl;
			cout << "-------------------------------------------------------------------------------" << endl;
			cout << endl;
			cout << argv[0] << " <source> <destination> [/A] [/D] [/DATE] [E] [/F] [/I] [/M] [/O]" << endl;
			cout << "                              [/Q] [/R] [/S] [/W] [/WCOPY]" << endl << endl;
			cout << " source         Specifies the folder with the file(s) to copy." << endl;
			cout << " destination    Specifies were the file(s) should be copied to." << endl;
			cout << " /A             Sets the destination files(a) to achive attribute." << endl;
			cout << " /D             When set only folders structures are created." << endl;
			cout << " /DATE          set first folder as current date." << endl;
			cout << " /E             Create folder structures and zero-length files only." << endl;
			cout << " /F             When set displays the full filename being copied." << endl;
			cout << " /I             When set file status is shown." << endl;
			cout << " /M             Move files (delete from source after copying)." << endl;
			cout << " /O             Writes over files that are read-only." << endl;
			cout << " /Q             When set no messages are displayed while copying." << endl;
			cout << " /R             Include full relative paths in destination path." << endl;
			cout << " /S             When set all sub folders are included in backup." << endl;
			cout << " /W             Displays a message and waits for a key press." << endl;
			cout << " /WCOPY         Copies files using WinApi file attributes are not changed." << endl;
			cout << endl;
			exit(1);
		}
		exit(1);
	}
	//
	//Check argv
	if (argc < 3){
		cout << "Usage: " << argv[0] << " <source> <destination> [options]" << endl << endl;
		cout << "For more information see " << argv[0] << " /?" << endl;
		exit(1);
	}

	//Get other args
	while (i < argc){
		Flags = LCase(argv[i]);

		//Include date stamp
		if (Flags == "/date"){
			DateStamp = true;
		}

		//All files attributes are set to achive when set
		if (Flags == "/a"){
			SetAchiveOnlyAttr = true;
		}
		//Create zero length files.
		if (Flags == "/e"){
			ZeroLenFiles = true;
		}
		if (Flags == "/r"){
			IncludeFullRelativePath = true;
		}
		//If set all sub folders are scanned.
		if (Flags == "/s"){
			ScanAllDirs = true;
		}
		//Ovwerwrite read only files.
		if (Flags == "/o"){
			OverReadOnly = true;
		}
		//if set no on-screen messages are shown
		if (Flags == "/q"){
			NoMsg = true;
		}
		//When set only folder structure is created.
		if (Flags == "/d"){
			bCopyFiles = false;
		}

		//Displays full filenames
		if (Flags == "/f"){
			ShowFullName = true;
		}
		//Display file status if set
		if (Flags == "/i"){
			ShowStatusInfo = true;
		}
		//Display a message before copying if set
		if (Flags == "/w"){
			ShowStartMsg = true;
		}
		//Test if using windows internal copyfile function.
		if (Flags == "/wcopy"){
			UseWinCopy = true;
		}
		//Move files
		if (Flags == "/m"){
			m_Action = F_MOVE;
		}
		//INC counter
		i++;
	}

	//Display copying message.
	if (ShowStartMsg){
		cout << "The copy process is about to begin press any key to continue." << endl;
		while (1){
			if (kbhit()){
				break;
			}
		}
	}

	if (ShowStatusInfo){
		sStatusA = "....OK";
		sStatusB = "....Skipped";
	}

	//Source folder
	Temp = argv[1];

	//Destination path
	DeskFolder = FixPath(argv[2]);

	//Include date stamp
	if (DateStamp){
		cout << DateToStr().c_str() << endl;
		DeskFolder += DateToStr() + '\\';
	}

	//Include source's path in destination path
	if (IncludeFullRelativePath){
		DeskFolder = DeskFolder + Temp[0] + '\\';
	}

	//Check for file type
	pos = Temp.find('*');

	if (pos != string::npos){
		fExt = TrimAll(LCase(Temp.substr(pos + 1)));
		if (fExt == ".*"){
			fExt.clear();
		}
		//Fix the path
		Temp.erase(pos);
		//Trim any remaining spaces
		Temp = TrimAll(Temp);
	}
	else{
		//Default copy all file types.
		Temp = TrimAll(Temp);
	}

	//Call DirTree procedure
	DirTree(Temp, DeskFolder, fExt, ScanAllDirs);

	if (!NoMsg){
		//Display results
		cout << endl;

		//Test if copying filees.
		if (m_Action == F_COPY){
			//Copying files message.
			cout << "Copyed  : " << Copyed << endl;
		}
		else{
			//Update message for moving files.
			cout << "Moved  : " << Copyed << endl;
		}
		cout << "Skipped : " << Skipped << endl;
	}

	return EXIT_SUCCESS;
}
